﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;


namespace KeyWordSearch.TreeParser
{
	[System.Diagnostics.DebuggerStepThrough]
	internal class TokenStream
	{
		private class CharcterStream
		{
			private StringInfo Info_;

			public CharcterStream(string source)
			{
				Info_ = new StringInfo(source);
				CurrentTextIndex = -1;
				CurrentIndex = -1;
			}

			public bool MoveNext()
			{
				if (++CurrentTextIndex >= Info_.LengthInTextElements)
				{
					CurrentTextIndex = Info_.LengthInTextElements;
					CurrentIndex = Info_.String.Length;
					return false;
				}
				else
				{
					if (CurrentIndex == -1)
					{
						CurrentIndex = 0;
					}
					else
					{
						CurrentIndex += this.Current.Length;
					}

					this.Current = Info_.SubstringByTextElements(CurrentTextIndex, 1);
					return true;
				}
			}

			public bool MovePrevious()
			{
				if (CurrentTextIndex == -1)
				{
					throw new InvalidOperationException();
				}

				--CurrentTextIndex;

				if (CurrentTextIndex <= 0)
				{
					CurrentIndex = 0;
					CurrentTextIndex = 0;
					return false;
				}
				else
				{
					this.Current = Info_.SubstringByTextElements(CurrentTextIndex, 1);
					this.CurrentIndex -= this.Current.Length;
					return true;
				}
			}


			public string Current
			{
				get;
				private set;
			}

			public int CurrentTextIndex
			{
				get;
				private set;
			}

			public int CurrentIndex
			{
				get;
				private set;
			}
			
			public string Souce
			{
				get
				{
					return Info_.String;
				}
			}

			public string Peek
			{
				get
				{
					if ((CurrentTextIndex + 1) > Info_.LengthInTextElements)
					{
						return null;
					}
					else
					{
						return Info_.SubstringByTextElements((CurrentTextIndex + 1), 1);
					}
				}
			}

		}


		static HashSet<string> SkipValues_;
		static Dictionary<string, TokenType> TypeDict_;
		static HashSet<string> IdentChars_;

		static TokenStream()
		{
			SkipValues_ = new HashSet<string>();
			TypeDict_ = new Dictionary<string, TokenType>();
			IdentChars_ = new HashSet<string>();

			SkipValues_.Add(" ");
			SkipValues_.Add("\t");
			SkipValues_.Add("　");

			TypeDict_.Add("(", TokenType.LeftBracket);
			TypeDict_.Add(")", TokenType.RightBracket);
			TypeDict_.Add(",", TokenType.Comma);
			TypeDict_.Add("|", TokenType.OperatorOR);
			TypeDict_.Add("&", TokenType.OperatorAND);
			TypeDict_.Add("!", TokenType.OperatorNOT);

			for (char i = (char)0x0041; i <= (char)0x005A; i++)
			{
				IdentChars_.Add(i.ToString());
			}

			for (char i = (char)0x0061; i <= (char)0x007A; i++)
			{
				IdentChars_.Add(i.ToString());
			}

			IdentChars_.Add("_");
		}

		private List<Token> Buffer_;
		private int CurrentIndex_;

		public TokenStream(string source)
		{
			if(source==null)
			{
				throw new ArgumentNullException("source");
			}

			CharcterStream str = new CharcterStream(source);
			this.CurrentIndex_ = -1;

			CreateTokenBuffer(str);
		}

		private void CreateTokenBuffer(CharcterStream str)
		{
			Buffer_ = new List<Token>();

			while (str.MoveNext())
			{
				if (str.Current == "&")
				{
					BuildOpAnd(str);
				}
				else if (str.Current == "|")
				{
					BuildOpOr(str);
				}
				else if (str.Current == "\"")
				{
					BuildStringLiteral(str);
				}
				else if (TypeDict_.ContainsKey(str.Current))
				{
					Buffer_.Add(new Token(str.Current, TypeDict_[str.Current], str.CurrentIndex, 1, str.CurrentIndex, str.Current.Length));
				}
				else if (IdentChars_.Contains(str.Current))
				{
					BuildIdent(str);
				}
				else if (SkipValues_.Contains(str.Current))
				{
					continue;
				}
				else
				{
					throw new LexicalException(
						string.Format("定義外の文字{0}を検知しました", str.Current), str.Souce, str.CurrentIndex, 1);
				}
			}

			Buffer_.Add(new Token("EOF", TokenType.EOF, str.CurrentIndex, 0, str.CurrentIndex, 0));
			
		}

		private void BuildIdent(CharcterStream str)
		{
			StringBuilder bld = new StringBuilder();
			bld.Append(str.Current);
			int textStartIdx = str.CurrentTextIndex;
			int StartIdx = str.CurrentIndex;



			while (str.MoveNext())
			{
				if (IdentChars_.Contains(str.Current))
				{
					bld.Append(str.Current);
				}
				else
				{
					str.MovePrevious();
					break;
				}
			}

			Buffer_.Add(new Token(bld.ToString(), TokenType.Identifyier, textStartIdx, 1 + str.CurrentTextIndex - textStartIdx, StartIdx, 1 + str.CurrentIndex - StartIdx));
		}

		private void BuildStringLiteral(CharcterStream str)
		{
			StringBuilder bld = new StringBuilder();
			bld.Append(str.Current);
			bool Flag = false;

			int TextStartIdx = str.CurrentTextIndex;
			int StartIdx = str.CurrentIndex;

			while (str.MoveNext())
			{
				if (str.Current == "\\")
				{
					if (str.Peek != null)
					{
						bld.Append(str.Current);
						bld.Append(str.Peek);
					}
					else
					{
						throw new LexicalException("エスケープが不正です。", str.Souce, str.CurrentTextIndex, 0);
					}
					str.MoveNext();
					continue;
				}
				else if (str.Current == "\"")
				{
					bld.Append(str.Current);
					Flag = true;
					break;
				}
				else
				{
					bld.Append(str.Current);
				}
			}

			if (!Flag)
			{
				throw new LexicalException("文字列が\"で閉じられていません。", str.Souce, str.CurrentTextIndex, 0);
			}

			Buffer_.Add(new Token(bld.ToString(), TokenType.StringLiteral, TextStartIdx, 1 + str.CurrentTextIndex - TextStartIdx, StartIdx, 1 + str.CurrentIndex - StartIdx));
		}

		private void BuildOpOr(CharcterStream str)
		{
			if (str.Peek == "|")
			{
				Buffer_.Add(new Token("||", TokenType.OperatorShortCutedOR, str.CurrentTextIndex, 2, str.CurrentIndex, 2));
				str.MoveNext();
			}
			else
			{
				Buffer_.Add(new Token("|", TokenType.OperatorOR, str.CurrentTextIndex, 1, str.CurrentIndex, 1));
			}
		}

		private void BuildOpAnd(CharcterStream str)
		{
			if (str.Peek == "&")
			{
				Buffer_.Add(new Token("&&", TokenType.OperatorShortCutedAND, str.CurrentTextIndex, 2, str.CurrentIndex, 2));
				str.MoveNext();
			}
			else
			{
				Buffer_.Add(new Token("&", TokenType.OperatorAND, str.CurrentTextIndex, 1, str.CurrentIndex, 1));
			}
		}


		public bool MoveNext()
		{
			if (++CurrentIndex_ > Buffer_.Count)
			{
				--CurrentIndex_;
				return false;
			}
			else
			{
				return true;
			}
		}

		public void MovePrevious()
		{
			if (--CurrentIndex_ < 0)
			{
				CurrentIndex_ = 0;
			}
		}

		public void Reset()
		{
			CurrentIndex_ = -1;
		}

		public Token Current
		{
			get
			{
				if (CurrentIndex_ < 0)
				{
					throw new InvalidOperationException();
				}
				else if (CurrentIndex_ >= Buffer_.Count)
				{
					return null;
				}
				else
				{
					return Buffer_[CurrentIndex_];
				}
			}
		}

		public Token Peek
		{
			get
			{
				if ((CurrentIndex_ + 1) > Buffer_.Count)
				{
					return null;
				}
				else
				{
					return Buffer_[CurrentIndex_ + 1];
				}
			}
		}
	}
}

